準確一點應該是 Stateless function,更語意一點就是 Avoid Shared State,因為一個系統中不太可能全部都是 Stateless 的,還是要有地方存這些狀態。
Stateless is any variable, object, or memory space that exists in a shared scope, or as the property of an object being passed between scopes.
shared scope 可以包含在 global scope 或是 function scope (恩,其實 JS 總共也才這兩個 scope)。
以前很習慣 Shared state (Stateful),不管是 global scope 裡的變數或增加/減少 object 裡的屬性。
// words = ['world', 'fp', 'to', 'welcome']
let keepers = []
for(let i in words) {
if(words[i].length > 3) {
keepers.push(words[i])
} // ['world', 'welcome']
}
// 程式大家一起 Share keepers 這個 Array
Shared state 最大問題就是你必須很清楚每一行程式碼在做什麼,什麼時候做了什麼事變動了 state,並且要記憶 state 變成多少 。一但系統變複雜或是有多個工程師共同開發就必須花大量時間去弄懂程式碼在幹嘛 (崩潰狀)
// Stateful
const x = 4;
x++; // x 變 5
// 省略 100 行...
x*2 // 完全不知道哪行改過 x,現在到底變成什麼
若還加上 promise 、setTimeout 的運算更難掌握處理 State 的先後次序 (就像上圖中 state 並不是獨立存在而是互相依賴在別的運算或函式中); 但若改成 FP 習慣的 Stateless function,你完全不用去記憶 state 到底是什麼,因為他只存在單一函數中,執行完 state 就被丟掉了
// Stateless
_.filter(x => x.length > 3), // 不用記憶 x 是什麼
另外一個缺點是 Share State 會有順序與執行次數問題
const x = {
val: 2
};
const x1 = () => x.val += 1;
const x2 = () => x.val *= 2;
x2();
x1();
console.log(x.val); // 5
x2();
x1();
console.log(x.val); // 11
以上範例會發現執行次數影響結果,那改成 Shareless 呢
const x = {
val: 2
};
const x1 = x => Object.assign({}, x, { val: x.val + 1});
const x2 = x => Object.assign({}, x, { val: x.val * 2});
console.log(x1(x2(x)).val); // 5
x2(x);
x1(x);
// 那順序改變呢
console.log(x1(x2(x)).val); // 5
就會發現不管執行幾次結果都還是一樣
這一篇比較簡短一點(我不會說出來,其實是文章庫存都被我用完了 orz),由範例可以看到 Share State 通常資料也會是 Immutable 的,然後 Immutable data 又容易產生無法預期的 Side Effect,這一些 Buzz Words 其實都是環環相扣的啊
如有錯誤或需要改進的地方,拜託跟我說。
我會以最快速度修改,感謝您
歡迎追蹤我的部落格,除了技術文也會分享一些在矽谷工作的甘苦。
嗨~
以下一些我的看法,供參考:
在改成 Shareless 那個部分的這註解 // 那順序改變呢
看起來怪怪的。
在想那個範例比較像是在說明:「不管執行幾次都一樣」。
順序的問題比較像以下兩者的差異:
Shared State:
const x = { val: 2 }
const x1 = () => (x.val += 1)
const x2 = () => (x.val *= 2)
const a = (() => {
x2()
x1()
return x.val
})()
const b = (() => {
x2()
x1()
return x.val
})()
console.log(a, b) // 5 11
const x = { val: 2 }
const x1 = () => (x.val += 1)
const x2 = () => (x.val *= 2)
const b = (() => {
x2()
x1()
return x.val
})()
const a = (() => {
x2()
x1()
return x.val
})()
console.log(a, b) // 11 5
Stateless:
const x = { val: 2 }
const x1 = (x) => Object.assign({}, x, { val: x.val + 1 })
const x2 = (x) => Object.assign({}, x, { val: x.val * 2 })
const a = (() => x1(x2(x)).val)()
const b = (() => x1(x2(x)).val)()
console.log(a, b) // 5 5
const x = { val: 2 }
const x1 = (x) => Object.assign({}, x, { val: x.val + 1 })
const x2 = (x) => Object.assign({}, x, { val: x.val * 2 })
const b = (() => x1(x2(x)).val)()
const a = (() => x1(x2(x)).val)()
console.log(a, b) // 5 5